#include <queue>
#include <iostream>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

#define MAX_THREAD 5
#define TASK_NUM 5

template<class T>
class Task{
  public:
    Task(T _data, void (*_handler)(T))
      :data(_data), handler(_handler){}
    
    void SetTask(T data, void (*handler)(T)){
      this->data = data;
      this->handler = handler;
    }

    void Run(){
      handler(data);
    }
  private:
    T data;
    void (*handler)(T);
};

template<class T>
class ThreadPool{
  public:
    ThreadPool(int max = MAX_THREAD)
    :max_thread(max), cur_thread(max), is_exit(false){
      pthread_mutex_init(&lock, NULL);
      pthread_cond_init(&cond, NULL);
      PoolInit();
    }
    ~ThreadPool(){
      pthread_mutex_destroy(&lock);
      pthread_cond_destroy(&cond);
    }
    
    bool PoolInit(){
      for(int i = 0; i < max_thread; ++i){
        int ret = pthread_create(&tid[i], NULL, ThreadStart, this);
        if(ret != 0){
          printf("%s\n", strerror(ret));
          return false;
        }
      }
    }

    bool PushTask(Task<T> *task){
      Lock();
      if(is_exit){
        UnLock();
        return false;
      }
      TaskQueue.push(task);
      UnLock();
      WakeUp();
      return true;
    }
    
    void PopTask(Task<T> **task){
      *task = TaskQueue.front();
      TaskQueue.pop();
    }

    void PoolQuit(){
      Lock();
      is_exit = true;
      UnLock();
      while(cur_thread > 0){
        WakeUpAll();
        sleep(1);
      }
      for(int i = 0; i < MAX_THREAD; ++i){
        pthread_join(tid[i], NULL);
      }
    }

    static void* ThreadStart(void* arg){
      ThreadPool* tp = (ThreadPool*)arg;
      while(1){
        tp->Lock();
        while(tp->IsEmpty()){       
          if(tp->is_exit){
            tp->ThreadQuit();
          }
          tp->Wait();
        }
        Task<T> *task;
        tp->PopTask(&task);
        tp->UnLock();
        task->Run();
        delete task;
      }
      return NULL;
    }

  private:
    bool IsEmpty(){
      return TaskQueue.empty();
    }

    void Lock(){
      pthread_mutex_lock(&lock);
    }

    void UnLock(){
      pthread_mutex_unlock(&lock);
    }

    void Wait(){
      pthread_cond_wait(&cond, &lock);
    }

    void WakeUp(){
      pthread_cond_signal(&cond);
    }

    void WakeUpAll(){
      pthread_cond_broadcast(&cond);
    }

    void ThreadQuit(){
      --cur_thread;
      UnLock();
      printf("Thread[%p] exit\n", pthread_self());
      pthread_exit(NULL);
    }
  private:
    int max_thread;
    int cur_thread;
    bool is_exit;
    std::queue<Task<T>*> TaskQueue;
    pthread_t tid[MAX_THREAD];
    pthread_mutex_t lock;
    pthread_cond_t cond;
};

void handler(int data){
  printf("Thread[%p], Run[%d]\n", pthread_self(), data);
  sleep(2);
}

int main(){
  ThreadPool<int> tp;
  for(int i = 0; i < TASK_NUM; ++i){
    Task<int>* task = new Task<int>(i, handler);
    tp.PushTask(task);
  }
  sleep(1);
  tp.PoolQuit();
  return 0;
}
